package xcrypto

import (
	"bufio"
	"fmt"
	"os"
	"strings"

	. "gitee.com/shuohe/xutils"
	"gitee.com/shuohe/xutils/xcrypto/base58"
	"gitee.com/shuohe/xutils/xerror"
)

func errFileData() error {
	return xerror.SimpleNew(Caller(2) + ": Corrupted file data.")
}

type KeyItem struct {
	Alias string
	Key   IKey
}

type KeySet struct {
	aliasMap map[string]*KeyItem
}

func (this *KeySet) Add(alias string, key IKey) error {
	// 检查重复名称
	if this.aliasMap[alias] != nil {
		return errDuplicatedKeyAlias(alias)
	}

	this.aliasMap[alias] = &KeyItem{alias, key}

	return nil
}

func (this *KeySet) Get(alias string) (*KeyItem, error) {
	ki, ok := this.aliasMap[alias]
	if !ok {
		return nil, errKeyNotFound(alias)
	}

	return ki, nil
}

func (this *KeySet) Get2(alias string) *KeyItem {
	ki, err := this.Get(alias)
	if err != nil {
		panic(err)
	}

	return ki
}

func (this *KeySet) Iterate(action func(*KeyItem) bool) {
	for _, item := range this.aliasMap {
		if !action(item) {
			break
		}
	}
}

func (this *KeySet) Save(filename string) (int, error) {
	f, err := os.Create(filename)
	if err != nil {
		return 0, xerror.Wrap(err, "Can not create file for write.")
	}
	defer f.Close()

	n := 0
	for _, item := range this.aliasMap {
		s := base58.Encode(item.Key.Export())
		fmt.Fprintln(f, item.Alias, s)
		n++
	}

	return n, nil
}

func (this *KeySet) Save2(filename string) int {
	n, err := this.Save(filename)
	if err != nil {
		panic(err)
	}

	return n
}

func (this *KeySet) Load(filename string) (int, error) {
	f, err := os.Open(filename)
	if err != nil {
		return 0, xerror.Wrap(err, "Can not open file for read.")
	}
	defer f.Close()

	n := 0
	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line == "" {
			continue
		}
		words := strings.Split(line, " ")
		if len(words) != 2 {
			return n, errFileData()
		}

		alias := words[0]
		data, err := base58.Decode(words[1])
		if err != nil {
			return n, errFileData()
		}

		key, err := LoadKey(data)
		if err != nil {
			return n, xerror.Wrap(err, "Failed to load key data.")
		}
		err = this.Add(alias, key)
		if err != nil {
			return n, xerror.Wrap(err, "Failed to add key.")
		}

		n++
	}
	if err := scanner.Err(); err != nil {
		return n, xerror.Wrap(err, "Failed to read file.")
	}

	return n, nil
}

//func (this *KeySet) Load2(filename string) int {
//    n, err := this.Load(filename)
//    if err != nil {
//        panic(err)
//    }
//
//    return n
//}

func NewKeySet() *KeySet {
	return &KeySet{make(map[string]*KeyItem)}
}
